package com.min.redis.common.util.redis;

import com.min.redis.common.enums.redis.LockType;
import com.min.redis.common.util.LogUtils;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

import static com.min.redis.common.enums.redis.LockType.REENTRANT_LOCK;

/**
 * @author wangmin
 * @date 2023/12/31
 */
@Component
@Slf4j
public class RedissonUtil {

    // RedissonClient已经由配置类生成，这里自动装配即可
    @Autowired
    private RedissonClient redissonClient;

    /**
     * 统一前缀
     */
//    @Value("${redis.prefix}")
    private String prefix;

    /**
     * 获取锁
     *
     * @param key 锁前缀
     * @param lockType 锁类型
     * @return
     */
    private RLock getLock(String key, LockType lockType) {
        RLock lock;
        String lockKey = key;
        switch (lockType){
            // 可重入锁
            case REENTRANT_LOCK:
                lock=  redissonClient.getLock(lockKey);
                break;
            //公平锁
            case FAIR_LOCK:
                lock=  redissonClient.getFairLock(lockKey);
                break;
            //读锁
            case READ_LOCK:
                lock=  redissonClient.getReadWriteLock(lockKey).readLock();
                break;
            //写锁
            case WRITE_LOCK:
                lock=  redissonClient.getReadWriteLock(lockKey).writeLock();
                break;
            default:
                throw new RuntimeException("do not support lock type：" + lockType);
        }
        return lock;
    }

    /**
     * 直接上锁
     * @param key
     * @return
     */
    public ILock lock(String key) {
        return this.lock(key, 0L, TimeUnit.SECONDS, REENTRANT_LOCK);
    }

    /**
     * 设置条件上锁
     * @param
     * @param lockTime 锁时间
     * @param unit  时间单位
     * @param lockType 锁类型
     * @return
     */
    public ILock lock(String key, long lockTime, TimeUnit unit, LockType lockType) {
        RLock lock = getLock(key, lockType);
        // 获取锁,失败一直等待,直到获取锁,不支持自动续期
        if (lockTime > 0L) {
            lock.lock(lockTime, unit);
        } else {
            // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
            lock.lock();
        }
        log.debug("上锁成功，key为[" + key + "],是否是公平锁+[" + lockType + "]");
        return new ILock(lock, this);
    }


    /**
     * 获取rlock锁
     * @param lockKey  key
     * @param leaseTime 释放时间
     * @return
     */
    public RLock lock(String lockKey, long leaseTime) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(leaseTime, TimeUnit.SECONDS);
        return lock;
    }

    /**
     * timeout为加锁时间，时间单位由unit确定
     * @param lockKey
     * @param unit
     * @param timeout
     * @return org.redisson.api.RLock
     */
    public RLock lock(String lockKey, TimeUnit unit, long timeout) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.lock(timeout, unit);
        return lock;
    }

    /**
     * 尝试获取锁
     * @param lockType 锁的类型
     * @param lockKey 锁的key
     * @param unit 锁的单位
     * @param waitTime 获取锁等待时间
     * @param leaseTime 锁自动释放时间
     * @return boolean
     */
    public ILock tryLock(LockType lockType, String lockKey, TimeUnit unit, long waitTime, long leaseTime) {
        RLock lock = getLock(lockKey,lockType);
        try {
            boolean lockAcquired;
            // 尝试获取锁，获取不到超时异常,不支持自动续期
            if (leaseTime > 0L) {
                lockAcquired = lock.tryLock(waitTime, leaseTime, unit);
            } else {
                // 具有Watch Dog 自动延期机制 默认续30s 每隔30/3=10 秒续到30s
                lockAcquired = lock.tryLock(waitTime, unit);
            }
            if (lockAcquired) {
                return new ILock(lock, this);
            }
        } catch (InterruptedException e) {
            return null;
        }
        return null;
    }

    /** 如果未获得锁，等待的超时时间，单位为毫秒。如果为-1,则尝试获得锁失败时立刻返回false; 如果为0，则永不超时
     * 如果等于0表示无限重试，小于0只尝试获取一次锁，大于0重试直到超时
     * 尝试获取锁
     * 锁的类型 : 默认可重入锁  锁的时间单位 ： 毫秒
     * @param lockKey 锁的key
     * @param waitTime 获取锁等待时间
     * @param leaseTime 锁自动释放时间
     * @return boolean
     */
    public ILock tryLock(String lockKey, long leaseTime, long waitTime) {
        leaseTime = leaseTime <= 0 ? 30000 : leaseTime;
        RLock lock = getLock(lockKey,REENTRANT_LOCK);
        try {
            // 锁标识
            boolean lockAcquired;
            // 为0永不超时，一直获取
            if (waitTime == 0L){
                lockAcquired = lock.tryLock(50L, leaseTime, TimeUnit.MILLISECONDS);
                if (!lockAcquired){
                    tryLock(lockKey,leaseTime,waitTime);
                }
            }
            // 大于0 则重试到超时
            if (waitTime > 0L) {
                lockAcquired = lock.tryLock(waitTime, leaseTime, TimeUnit.MILLISECONDS);
            } else {
                // 小于0 则试一次，立马重试
                lockAcquired = lock.tryLock(50L, leaseTime, TimeUnit.MILLISECONDS);
            }
            if (lockAcquired) {
                return new ILock(lock, this);
            }
        } catch (InterruptedException e) {
            LogUtils.COMMON.error("尝试获取锁失败",e);
            return null;
        }
        return null;
    }

    /**
     * 通过lock对象解锁
     * @param lock
     */
    public void unlock(Object lock) {
        if (lock == null) {
            log.warn("释放分布式锁失败，锁对象不存在");
            return;
        }
        ILock iLock = (ILock) lock;
        if (!(iLock.getLock() instanceof RLock)) {
            throw new IllegalArgumentException("Invalid lock object");
        }
        RLock rLock = (RLock)((ILock) lock).getLock();
        if (rLock.isLocked()) {
            try {
//                log.debug("解锁rlock对象为：" + JSON.toJSONString(rLock));
                rLock.unlock();
            } catch (IllegalMonitorStateException e) {
                log.error("释放分布式锁异常", e);
            }
        }
    }

    /**
     * 通过lockKey解锁
     * @param lockKey
     * @return void
     */
    public void unlock(String lockKey) {
//        RLock lock = this.getLock(lockKey, REENTRANT_LOCK);
//        lock.unlock();
    }

    public static void shutdown(){
//        if (redissonClient != null){
//            redissonClient.shutdown();
//        }
    }
}
